#ifndef MATRIX_HPP
#define MATRIX_HPP

#include <iostream>

void createMatrix( float** data, int xSize, int ySize );
void releaseMatrix( float* data );

void uploadMatrix( float* data, float* gpuData, int xSize, int ySize );
void downloadMatrix( float* data, float* gpuData, int xSize, int ySize );

void copyMatrix( float* sourceMatrix, float* destMatrix, int xSize, int ySize, int destXSize, int destYSize, int xOffset, int yOffset );
void subMatrix( float* sourceMatrix, float* destMatrix, int xSize, int ySize, int destXSize, int destYSize, int xOffset, int yOffset );
void mulMatrix( float* destMatrix, float* sourceMatrix, float* sourceMatrix2, int xSize, int ySize, int zSize ); 
void getSubmatrix( float* submatrix, float* matrix, int xOffset, int yOffset, int xSize, int ySize, int srcXSize, int srcYSize );
void makeLUStep( float* matrix, float* lMatrix, int baseLint, int xSize, int ySize );
void generateIdentityMatrix( float* matrix, int size );
void solveSystemRightStep( float* uMatrix, float* lMatrix, int xSize, int ySize, int zSize, int offset );
void systemRightStep( float* matrix, float* uMatrix, int xSize, int ySize, int zSize, int offset );
void clearUp( float* matrix, int size, int offset, int cleanOffset, int cleanSize );
void scaleRow( float* matrix, float* uMatrix, int xSize, int ySize, int scaleX, int scaleY, int row ); 

class Matrix
{
public:
  Matrix( int n, int m );
  
  void clear();
  
  void upload();
  void download();
  
  void print( std::ostream& out = std::cout );
  
  static Matrix generate( int size );
  static Matrix generateL( int size );
  static Matrix generateU( int size );
  
  //GPU
  static Matrix generateIdentity( int size );
  
  static Matrix load( const char* filename );
  
  //GPU
  Matrix mul( Matrix matrix );
  void mul( Matrix matrix, Matrix *result );
  
  //GPU
  Matrix copy();
  void copy( Matrix *result, int yOffset=0, int xOffset=0 );
  
  //GPU
  Matrix submatrix( int xOffset, int yOffset, int xSize, int ySize );
  
  int N();
  int M();
  
  void doLU( Matrix lMatrix );
  void doLUBlock( Matrix lMatrix );
  
  bool isEqual( Matrix matrix );
  
private:
  
  void solveSystemRight( Matrix lMatrix );
  void solveSystemLeft( Matrix uMatrix );
  
  void doLUStep( Matrix lMatrix, int baseLine=0 );
  void doLUBlockStep( Matrix lMatrix, int offset=0 );
  
  void solveSystemLeftStep( Matrix uMatrix, int offset=0 );
  
  float *_values;
  float *_valuesGPU;
  int _n;
  int _m;
  
};

#endif